# --*--utf8--*--
from tkinter import *
from tkinter.ttk import *
import tkinter.messagebox as messageBox
# import tkinter.tix
from ctypes import *
import collections
import time
from concurrent.futures import ThreadPoolExecutor
import threading
from abc import abstractmethod
import json
from .funcbase import UnitTestDataStructure
from .functest import *
from ..utils.loggers import dev_logger, prod_logger
from .helpdoc.window import app_window as helpdoc_app_window
from ..utils.utils import unitTestRecvQueues, executor_pool_exception_callback, RecvMsg


from ..usbcan2.usbcan2 import UsbcanConstant, BoardInfo, CAN_OBJ, INIT_CONFIG, ECAN

# 定义抽象基类做基本设置把需要单独设置的放到业务类里
class GcanViewBase(Tk):
    # 主页面初始化
    def __init__(self):
        super().__init__()

        self.ecan = ECAN()
        self.musbcanopen = False
        self.rec_CAN1 = 1
        self.rec_CAN2 = 1
        self.manualReadCanCounts = 0
        # 创建进程池
        self.pool = ThreadPoolExecutor(max_workers=8, thread_name_prefix="GcanViewBase_pool")
        # 持续读取总线数据，有错误时候通过executor_pool_exception_callback返回到主线程中展示
        self.pool.submit(self.ReadCAN).add_done_callback(executor_pool_exception_callback)
        self.tkViewInit()
        # TODO: 最大长度需要合理设定
        self.recvMsgDeque = collections.deque(maxlen=200)
        self.recvMsgDequeLock = threading.Lock()

        # 离线模式设置
        self.sendcan1_manual_offline = False

    def tkViewInit(self):

        self.title("Gcan view (refactor by angelyhch)")
        self.resizable(width=True, height=True)
        # self.tk.eval("package require Tix")
        self.deviceConfigFrameInit()
        self.tabControlInit()

        # 新建用户窗口
        # 后续可以根据具体的测试需求更新展示内容
        self.userWindowInit()
        # 输入框获取焦点
        # FIXME :如果通过扫码获取焦点,就不需要一开始获取焦点,在获取到数据后再设定获取焦点
        # self.curVinCodeEntry.focus_set()
        
    def deviceConfigFrameInit(self):
        self.deviceConfigFrame = Frame(self)
        self.deviceConfigFrame.grid(row=0, column=0)
        self.lb1 = Label(
            self.deviceConfigFrame, text="CAN1波特率:", borderwidth=3, font=("Arial", 12)
        )
        self.lb1.grid(row=1, column=0, padx=1, pady=1, sticky="w")
        self.lb2 = Label(
            self.deviceConfigFrame, text="CAN2波特率:", borderwidth=3, font=("Arial", 12)
        )
        self.lb2.grid(row=2, column=0, padx=1, pady=1, sticky="w")
        self.lbsn = Label(
            self.deviceConfigFrame,
            text="SN:",
            borderwidth=3,
            font=("Arial", 12),
            width=30,
        )
        self.lbsn.grid(row=2, column=3, padx=5, pady=5, sticky="w")

        self.baudvaluecan1 = StringVar()
        self.baudvaluecan1.set("500k")
        self.baudvaluecan2 = StringVar()
        self.baudvaluecan2.set("500k")
        self.baudvalues = [
            "1M",
            "800k",
            "666k",
            "500k",
            "400k",
            "250k",
            "200k",
            "125k",
            "100k",
            "80k",
            "50k",
        ]
        self.can1com = Combobox(
            master=self.deviceConfigFrame,
            state="readonly",
            font=("Arial", 12),
            textvariable=self.baudvaluecan1,
            values=self.baudvalues,
        )
        self.can1com.grid(row=1, column=1, padx=1, pady=1, sticky="nw")
        self.can2com = Combobox(
            master=self.deviceConfigFrame,
            state="readonly",
            font=("Arial", 12),
            textvariable=self.baudvaluecan2,
            values=self.baudvalues,
        )
        self.can2com.grid(row=2, column=1, padx=1, pady=1, sticky="w")
        self.btopen = Button(self.deviceConfigFrame, text="打开设备", command=self.caninit)
        self.btopen.grid(row=1, column=2, padx=1, pady=1, sticky="w")
        self.btreadinfo = Button(
            self.deviceConfigFrame,
            text="读取设备信息",
            command=self.readmess,
            state="disabled",
        )
        self.btreadinfo.grid(row=2, column=2, padx=1, pady=1, sticky="w")

        # 隐藏主窗口按钮
        self.hideMainWindow =Button( self.deviceConfigFrame, text='隐藏主窗口', command=self.iconify )
        self.hideMainWindow.grid(row=2, column=10, padx=10, pady=10)

        # 打开帮助文档
        self.helpDocButton = Button(self.deviceConfigFrame, text='帮助文档',  command=lambda :helpdoc_app_window(self))
        self.helpDocButton.grid(row=2, column=11, padx=10, pady=10)

    def tabControlInit(self):
        self.tabcontrol = Notebook(self)
        self.tabcontrol.grid(row=3, columnspan=5, sticky="nw")
        self.tab1 = Frame(self.tabcontrol)
        self.tab2 = Frame(self.tabcontrol)
        self.tab5 = Frame(self.tabcontrol)
        self.tab6 = Frame(self.tabcontrol)
        self.tabcontrol.add(self.tab1, text="CAN1")
        self.tabcontrol.add(self.tab2, text="CAN2")
        self.tabcontrol.add(self.tab5, text="单元测试库")
        # self.tabcontrol.add(self.tab6,text="座椅测试面板")

        self.can1TabInit()
        self.can2TabInit()
        self.tab5InitTestPanel()
        # self.tab6InitTabSeatPanel()

    def can1TabInit(self):

        # 发送数据界面
        self.sendMessage1FrameInit()

        # 接收数据界面
        self.recvMessage1FrameInit()

    def sendMessage1FrameInit(self):
        self.lb_ID_CAN1 = Label(
            self.tab1, text="ID(Hex)", borderwidth=3, font=("Arial", 12)
        )
        self.lb_ID_CAN1.grid(row=0, column=0, sticky="w")
        self.e_ID_CAN1 = Entry(self.tab1, font=("Arial", 12))
        self.e_ID_CAN1.grid(row=1, column=0, sticky="w")
        self.e_ID_CAN1.insert(0, "5d3")
        self.ext_CAN1 = IntVar()
        self.cb_Ext_CAN1 = Checkbutton(
            self.tab1,
            text="Extended",
            variable=self.ext_CAN1,
            # TODO 字体设置
            # font=("Arial", 12),
        )
        self.cb_Ext_CAN1.grid(row=0, column=1, sticky="w")
        self.rtr_CAN1 = IntVar()
        self.cb_Rtr_CAN1 = Checkbutton(
            self.tab1,
            text="RTR",
            variable=self.rtr_CAN1,
            # TODO:
            # borderwidth=3,
            # font=("Arial", 12),
        )
        self.cb_Rtr_CAN1.grid(row=1, column=1, sticky="w")

        self.ct_Length_CAN1_label = Label(self.tab1, text='Length(0-8)')
        self.ct_Length_CAN1_label.grid(row=0, column=6, sticky='e')
        # self.ct_Length_CAN1 = tkinter.tix.Control(
        #     self.tab1, label="Length(0-8):", integer=True, max=8, min=0, value=8, step=1
        # )
        self.ct_Length_CAN1_data = IntVar(value=8)
        self.ct_Length_CAN1 = Spinbox(
            self.tab1, textvariable=self.ct_Length_CAN1_data, from_=0, to=8, increment=1)
        self.ct_Length_CAN1.grid(row=0, column=7, columnspan=4, sticky="w")
        self.lb_Data_CAN1 = Label(
            self.tab1, text="Data(Hex)", borderwidth=3, font=("Arial", 12)
        )
        self.lb_Data_CAN1.grid(row=0, column=3, columnspan=4, sticky="w")
        self.Data0_CAN1 = StringVar()
        self.e_Data0_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data0_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data0_CAN1.grid(row=1, column=3, padx=2, pady=1, sticky="w")
        self.Data0_CAN1.set("00")
        self.Data1_CAN1 = StringVar()
        self.e_Data1_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data1_CAN1,
            width=3,
            # borderwidth=3,
            font=("Arial", 12),
        )
        self.e_Data1_CAN1.grid(row=1, column=4, padx=2, pady=1, sticky="w")
        self.Data1_CAN1.set("00")
        self.Data2_CAN1 = StringVar()
        self.e_Data2_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data2_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data2_CAN1.grid(row=1, column=5, padx=2, pady=1, sticky="w")
        self.Data2_CAN1.set("00")
        self.Data3_CAN1 = StringVar()
        self.e_Data3_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data3_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data3_CAN1.grid(row=1, column=6, padx=2, pady=1, sticky="w")
        self.Data3_CAN1.set("00")
        self.Data4_CAN1 = StringVar()
        self.e_Data4_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data4_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data4_CAN1.grid(row=1, column=7, padx=2, pady=1, sticky="w")
        self.Data4_CAN1.set("12")
        self.Data5_CAN1 = StringVar()
        self.e_Data5_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data5_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data5_CAN1.grid(row=1, column=8, padx=2, pady=1, sticky="w")
        self.Data5_CAN1.set("00")
        self.Data6_CAN1 = StringVar()
        self.e_Data6_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data6_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data6_CAN1.grid(row=1, column=9, padx=2, pady=1, sticky="w")
        self.Data6_CAN1.set("00")
        self.Data7_CAN1 = StringVar()
        self.e_Data7_CAN1 = Entry(
            self.tab1,
            textvariable=self.Data7_CAN1,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data7_CAN1.grid(row=1, column=10, padx=2, pady=1, sticky="w")
        self.Data7_CAN1.set("00")
        self.bt_send_CAN1 = Button(
            self.tab1,
            text="发送数据",
            state="disabled",
            command=self.sendcan1,
        )
        self.bt_send_CAN1.grid(row=1, column=12, padx=2, pady=1)

    def recvMessage1FrameInit(self):
        self.recvMessage1Frame1 = Frame(self.tab1)
        self.recvMessage1Frame1.grid(row=2, column=0, columnspan=11)
        self.s1 = Scrollbar(self.recvMessage1Frame1, orient=VERTICAL)
        self.s1.grid(row=2, column=11, sticky="ns")
        self.listreadcan1 = Listbox(
            self.recvMessage1Frame1,
            font=("Arial", 12),
            height=20,
            width=90,
            yscrollcommand=self.s1.set,
        )
        self.listreadcan1.grid(row=2, column=0, columnspan=11, sticky="nw")
        self.s1.config(command=self.listreadcan1.yview)
        self.bt_clear_CAN1 = Button(
            self.recvMessage1Frame1,
            text="清空",
            command=self.clearcan1,
        )
        self.bt_clear_CAN1.grid(row=2, column=12, padx=2, pady=1)
        pass

    def can2TabInit(self):
        # 发送数据界面
        self.sendMessage2FrameInit()

        # 接收数据界面
        self.recvMessage2FrameInit()

    def sendMessage2FrameInit(self):
        self.lb_ID_CAN2 = Label(
            self.tab2, text="ID(Hex)", borderwidth=3, font=("Arial", 12)
        )
        self.lb_ID_CAN2.grid(row=0, column=0, sticky="w")
        self.e_ID_CAN2 = Entry(self.tab2, font=("Arial", 12))
        self.e_ID_CAN2.grid(row=1, column=0, sticky="w")
        self.e_ID_CAN2.insert(0, "00000001")
        self.ext_CAN2 = IntVar()
        self.cb_Ext_CAN2 = Checkbutton(
            self.tab2,
            text="Extended",
            variable=self.ext_CAN2,
        )
        self.cb_Ext_CAN2.grid(row=0, column=1, sticky="w")
        self.rtr_CAN2 = IntVar()
        self.cb_Rtr_CAN2 = Checkbutton(
            self.tab2,
            text="RTR",
            variable=self.rtr_CAN2,
        )
        self.cb_Rtr_CAN2.grid(row=1, column=1, sticky="w")
        self.ct_Length_CAN2_label = Label(self.tab2, text='Length(0-8)')
        self.cb_Rtr_CAN2.grid(row=1, column=1, sticky="w")
        # self.ct_Length_CAN2 = tkinter.tix.Control(
        #     self.tab2, label="Length(0-8):", integer=True, max=8, min=0, value=8, step=1
        # )
        self.ct_Length_CAN2_data = IntVar(value=8)
        self.ct_Length_CAN2 = Spinbox(
            self.tab2, textvariable=self.ct_Length_CAN2_data, from_=0, to=8, increment=1)
        self.ct_Length_CAN2.grid(row=0, column=7, columnspan=4, sticky="w")
        self.lb_Data_CAN2 = Label(
            self.tab2, text="Data(Hex)", borderwidth=3, font=("Arial", 12)
        )
        self.lb_Data_CAN2.grid(row=0, column=3, columnspan=4, sticky="w")
        self.Data0_CAN2 = StringVar()
        self.e_Data0_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data0_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data0_CAN2.grid(row=1, column=3, padx=2, pady=1, sticky="w")
        self.Data0_CAN2.set("00")
        self.Data1_CAN2 = StringVar()
        self.e_Data1_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data1_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data1_CAN2.grid(row=1, column=4, padx=2, pady=1, sticky="w")
        self.Data1_CAN2.set("01")
        self.Data2_CAN2 = StringVar()
        self.e_Data2_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data2_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data2_CAN2.grid(row=1, column=5, padx=2, pady=1, sticky="w")
        self.Data2_CAN2.set("02")
        self.Data3_CAN2 = StringVar()
        self.e_Data3_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data3_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data3_CAN2.grid(row=1, column=6, padx=2, pady=1, sticky="w")
        self.Data3_CAN2.set("03")
        self.Data4_CAN2 = StringVar()
        self.e_Data4_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data4_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data4_CAN2.grid(row=1, column=7, padx=2, pady=1, sticky="w")
        self.Data4_CAN2.set("04")
        self.Data5_CAN2 = StringVar()
        self.e_Data5_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data5_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data5_CAN2.grid(row=1, column=8, padx=2, pady=1, sticky="w")
        self.Data5_CAN2.set("05")
        self.Data6_CAN2 = StringVar()
        self.e_Data6_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data6_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data6_CAN2.grid(row=1, column=9, padx=2, pady=1, sticky="w")
        self.Data6_CAN2.set("06")
        self.Data7_CAN2 = StringVar()
        self.e_Data7_CAN2 = Entry(
            self.tab2,
            textvariable=self.Data7_CAN2,
            width=3,
            font=("Arial", 12),
        )
        self.e_Data7_CAN2.grid(row=1, column=10, padx=2, pady=1, sticky="w")
        self.Data7_CAN2.set("07")
        self.bt_send_CAN2 = Button(
            self.tab2,
            text="发送数据",
            state="disabled",
            command=self.sendcan2,
        )
        self.bt_send_CAN2.grid(row=1, column=12, padx=2, pady=1)

    def recvMessage2FrameInit(self):
        self.recvMessage1Frame2 = Frame(self.tab2)
        self.recvMessage1Frame2.grid(row=2, column=0, columnspan=11)
        self.s2 = Scrollbar(self.recvMessage1Frame2, orient=VERTICAL)
        self.s2.grid(row=2, column=11, sticky="ns")
        self.listreadcan2 = Listbox(
            self.recvMessage1Frame2,
            font=("Arial", 12),
            height=20,
            width=90,
            yscrollcommand=self.s2.set,
        )
        self.listreadcan2.grid(row=2, column=0, columnspan=11, sticky="nw")
        self.s2.config(command=self.listreadcan2.yview)
        self.bt_clear_CAN2 = Button(
            self.tab2,
            text="清空",
            command=self.clearcan2,
        )
        self.bt_clear_CAN2.grid(row=2, column=12, padx=2, pady=1)

    def tab5InitTestPanel(self):
        '''
        单元测试库的初始化
        '''
        self.tab5Frame1 = Frame(self.tab5)
        self.tab5Frame1.grid(row=1, column=1)
        self.tab5Frame1_1 = Frame(self.tab5Frame1)
        self.tab5Frame1_1.grid(row=1, column=1)
        # TODO: frame1 预留位置

        self.tab5Frame1_2 = Frame(self.tab5Frame1)
        self.tab5Frame1_2.grid(row=2, column=1)
        self.setModelNameAndFunction()
        for PosId in self.MODELIDLIST:
            self.tab5btnUnitTest = Button(
                self.tab5Frame1_2,
                text=self.testmodelDict[PosId].name,
                command=self.testfuncDict[PosId],
            )
            # PosId 是一个行列的两位数字符串
            self.tab5btnUnitTest.grid(row=PosId[0], column=PosId[1], padx=10, pady=10)
        pass

    # 生产单元测试的窗口实例，然后测试完成自动隐藏窗口
    def unitTestCommand(self,posId):
        def wrapper():
            def inner():
                # 设定最大重测次数
                test_counts = 3
                for i in range(test_counts):
                    # testfuncDict里面放的是单元测试的构造函数，运行时候会生产单元测试的面板,frame00就是实例
                    # 后续要将frame00窗口隐藏，以提供更好的体验。
                    frame00: FunctestUnit = self.testfuncDict[posId]()
                    dev_logger.debug('-----')

                    # TODO: frame00.allTest就是运行当前单元测试的批量测试， 也可以用btn的invoke模拟按钮被按下来实现(该方法暂未验证)
                    # 此处把allTest的return值拿到，用来在主界面显示
                    testResult = frame00.allTest()

                    # 拿到alltest的结果,此处是阻塞的，每个测试陆续产生结果展示出来，测试完了，下面一句隐藏窗口
                    # 这个阻塞流程是在下面的[res = self.pool.submit(inner)],这里的现场跑的,self.pool可以开8个子线程
                    resFlag,resString, result = testResult.result()

                    # 测试完成后隐藏窗口
                    frame00.withdraw()
                    
                    if resFlag:
                        return resFlag, resString,  result
                        break

                # 如果3个循环都走完了，仍然没有True
                return False, '测试失败', AlltestReturnType(False, "测试失败")

            # FIXME: 此处用一个单独的线程来跑，否则result()函数会把主线程阻塞，导致无法正常工作
            res = self.pool.submit(inner)
            res.add_done_callback(executor_pool_exception_callback)
            return res
            
        return wrapper

    @abstractmethod
    def setModelNameAndFunction(self):
        pass

    def clearcan1(self):
        self.listreadcan1.delete(0, END)

    def clearcan2(self):
        self.listreadcan2.delete(0, END)

    def caninit(self):
        self.musbcanopen, self.rec_CAN1, self.rec_CAN2
        if self.musbcanopen == False:
            initconfig = INIT_CONFIG()
            # 0x657 和 0x512 两个id的滤波设置
            # initconfig.acccode = 0x82400000  # 设置验收码
            # initconfig.accmask = 0x68Bfffff  # 设置屏蔽码


            if self.option_program =='seat':
                # 0x657 一个id的滤波设置
                # initconfig.acccode = 0xCAE00000  # 设置验收码
                # initconfig.accmask = 0x001FFFFF  # 设置屏蔽码

                # 0x657,0x651，0x656 三个id的滤波设置
                initconfig.acccode = 0xCA000000  # 设置验收码
                initconfig.accmask = 0x00FFFFFF  # 设置屏蔽码

                # 0x657,0x651，0x656,0x280 三个id的滤波设置X10XX0X0XXXX
                # initconfig.acccode = 0x40000000  # 设置验收码
                # initconfig.accmask = 0x9AFFFFFF  # 设置屏蔽码

            elif self.option_program =='hot':
                # 0x5E0 一个id的滤波设置
                # initconfig.acccode = 0xBC000000  # 设置验收码  
                # initconfig.accmask = 0x001FFFFF  # 设置屏蔽码
                # 0x5E0 和 0x7A3 两个id的滤波设置
                initconfig.acccode = 0xB4000000  # 设置验收码  
                initconfig.accmask = 0x487FFFFF  # 设置屏蔽码

            # initconfig.acccode = 0  # 设置验收码
            # initconfig.accmask = 0xffffffff  # 设置屏蔽码
            initconfig.filter = 1  # 设置滤波使能
            mbaudcan1 = self.baudvaluecan1.get()
            mbaudcan2 = self.baudvaluecan2.get()
            # 打开设备
            if self.ecan.OpenDevice(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex) != UsbcanConstant.STATUS_OK:
                prod_logger.warning("Error, 请先打开设备")
                tkinter.messagebox.showinfo("ERROR", "OpenDevice Failed!")
                return
            initconfig.timing0, initconfig.timing1 =UsbcanConstant.getTiming(mbaudcan1)
            # TODO: 正式使用时要把模式改为0
            initconfig.mode = 0
            # 初始化CAN1
            if self.ecan.InitCan(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1, initconfig) != UsbcanConstant.STATUS_OK:
                prod_logger.warning("Error, 请先打开设备")
                tkinter.messagebox.showinfo("ERROR", "InitCan CAN1 Failed!")
                self.ecan.CloseDevice(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex)
                return
            # 初始化CAN2
            initconfig.timing0, initconfig.timing1 =UsbcanConstant.getTiming(mbaudcan2)
            if self.ecan.InitCan(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel2, initconfig) != UsbcanConstant.STATUS_OK:
                prod_logger.warning("Error, 请先打开设备")
                tkinter.messagebox.showinfo("ERROR", "InitCan CAN2 Failed!")
                self.ecan.CloseDevice(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex)
                return
            if self.ecan.StartCan(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1) != UsbcanConstant.STATUS_OK:
                prod_logger.warning("Error, 请先打开设备")
                tkinter.messagebox.showinfo("ERROR", "StartCan CAN1 Failed!")
                self.ecan.CloseDevice(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex)
                return
            if self.ecan.StartCan(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel2) != UsbcanConstant.STATUS_OK:
                prod_logger.warning("Error, 请先打开设备")
                tkinter.messagebox.showinfo("ERROR", "StartCan CAN2 Failed!")
                self.ecan.CloseDevice(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex)
                return
            
            # 连入总线时，首先清空缓存区的帧数据
            self.clearBuffer1()
            self.clearBuffer2()

            self.musbcanopen = True
            self.rec_CAN1 = 1
            self.rec_CAN2 = 1
            self.btopen.configure(text="关闭设备")
            self.btreadinfo.configure(state="normal")
            self.bt_send_CAN1.configure(state="normal")
            self.bt_send_CAN2.configure(state="normal")
        else:
            self.musbcanopen = False
            self.ecan.CloseDevice(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex)
            self.btopen.configure(text="打开设备")
            self.lbsn.configure(text="SN:")
            self.btreadinfo.configure(state="disabled")
            self.bt_send_CAN1.configure(state="disabled")
            self.bt_send_CAN2.configure(state="disabled")

    def clearBuffer1(self):
        # 清空缓存区的帧数据
        self.ecan.ClearBuffer(UsbcanConstant.USBCAN2,UsbcanConstant.DevIndex, UsbcanConstant.Channel1)

    def clearBuffer2(self):
        # 清空缓存区的帧数据
        self.ecan.ClearBuffer(UsbcanConstant.USBCAN2,UsbcanConstant.DevIndex, UsbcanConstant.Channel2)

    def readmess(self):
        self.musbcanopen
        if self.musbcanopen == False:
            prod_logger.warning("Error, 请先打开设备")
            tkinter.messagebox.showinfo("ERROR", "请先打开设备")
        else:
            mboardinfo, ret = self.ecan.ReadBoardInfo(
                UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex
            )  # 读取设备信息需要在打开设备后执行
            if ret == UsbcanConstant.STATUS_OK:
                mstr = ""
                for i in range(0, 10):
                    mstr = mstr + chr(
                        mboardinfo.str_Serial_Num[i]
                    )  # 结构体中str_Serial_Num内部存放存放SN号的ASC码
                self.lbsn.configure(text="SN:" + mstr)

            else:
                self.lbsn.configure(text="Read info Fault")

    def ReadCAN(self):
        self.musbcanopen, self.rec_CAN1, self.rec_CAN2
        while True:
            if self.musbcanopen == True and self.manualReadCanCounts == 0:
                len, rec, ret = self.ecan.Receivce(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1, 30)
                # dev_logger.debug('len:'+ str(len) + '---ret:' + str(ret))
                # if len > 0 and ret == 1:
                if ret > 0:
                    for i in range(ret):
                        # dev_logger.debug(f'i:{i}')
                        mstr = "Rec: " + str(self.rec_CAN1)
                        self.rec_CAN1 = self.rec_CAN1 + 1
                        if rec[i].TimeFlag == 0:
                            mstr = mstr + " Time: "
                        else:
                            mstr = mstr + " Time:" + hex(rec[i].TimeStamp).zfill(8)
                        if rec[i].ExternFlag == 0:
                            thisId = hex(rec[i].ID).zfill(3)
                            mstr = mstr + " ID:" + thisId + " Format:Stand "
                        else:
                            thisId = hex(rec[i].ID).zfill(8)
                            mstr = mstr + " ID:" + thisId + " Format:Exten "
                        if rec[i].RemoteFlag == 0:
                            mstr = mstr + " Type:Data " + " Data: "
                            thisData = ""
                            for j in range(0, rec[i].DataLen):
                                # mstr = mstr + hex(rec[i].data[i]).zfill(2) + " "
                                # curbyte 是0x**的格式
                                curbyte = hex(rec[i].data[j]).zfill(2)
                                mstr = mstr + curbyte + " "
                                thisData += curbyte

                            # 把收到数据加工成8字节的16进制表示
                            dataList = thisData.split("0x")
                            recvData = "".join([x.zfill(2) for x in dataList[1:]])
                            # dev_logger.debug(f"收到ID:{thisId}--数据:{recvData}")
                            thisMsg = RecvMsg(thisId, recvData)
                            # 往定长队列中添加信息
                            # dev_logger.debug(thisMsg)
                            with self.recvMsgDequeLock:
                            # if 1:
                                # if thisMsg.id in ['0x5d3','0x657','0x280','0x570','0x512', '0x513', '0x514']:
                                if 1:
                                    self.recvMsgDeque.append(thisMsg)
                                    unitTestRecvQueues.append_msg_to_queues(thisMsg)
                                    # dev_logger.debug(thisMsg)
                                    # dev_logger.debug(self.recvMsgDeque)
                        else:
                            mstr = mstr + " Type:Romte " + " Data: Remote Request"

                        if self.listreadcan1.size() > 1000:
                            self.listreadcan1.delete(0, END)
                        self.listreadcan1.insert("end", mstr)
                        self.listreadcan1.see(self.listreadcan1.size())

                len2, rec2, ret2 = self.ecan.Receivce(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel2, 1)
                if len2 > 0 and ret2 == 1:
                    mstr = "Rec: " + str(self.rec_CAN2)
                    self.rec_CAN2 = self.rec_CAN2 + 1
                    if rec2[0].TimeFlag == 0:
                        mstr = mstr + " Time: "
                    else:
                        mstr = mstr + " Time:" + hex(rec2[0].TimeStamp).zfill(8)
                    if rec2[0].ExternFlag == 0:
                        mstr = (
                            mstr + " ID:" + hex(rec2[0].ID).zfill(3) + " Format:Stand "
                        )
                    else:
                        mstr = (
                            mstr + " ID:" + hex(rec2[0].ID).zfill(8) + " Format:Exten "
                        )
                    if rec2[0].RemoteFlag == 0:
                        mstr = mstr + " Type:Data " + " Data: "
                        for i in range(0, rec2[0].DataLen):
                            mstr = mstr + hex(rec2[0].data[i]).zfill(2) + " "
                    else:
                        mstr = mstr + " Type:Romte " + " Data: Remote Request"

                    if self.listreadcan2.size() > 1000:
                        self.listreadcan2.delete(0, END)
                    self.listreadcan2.insert("end", mstr)
                    self.listreadcan2.see(self.listreadcan2.size())

            time.sleep(0.001)

    def sendcan1(self):
        self.musbcanopen
        if self.musbcanopen == False:
            prod_logger.warning("Error, 请先打开设备")
            tkinter.messagebox.showinfo("ERROR", "请先打开设备")
        else:
            canobj = CAN_OBJ()
            canobj.ID = int(self.e_ID_CAN1.get(), 16)
            # canobj.DataLen = int(self.ct_Length_CAN1["value"])
            canobj.DataLen = int(self.ct_Length_CAN1_data.get())

            # 将输入的字符串,转换为int类型,第二个参数表示进制选择.
            canobj.data[0] = int(self.e_Data0_CAN1.get(), 16)
            canobj.data[1] = int(self.e_Data1_CAN1.get(), 16)
            canobj.data[2] = int(self.e_Data2_CAN1.get(), 16)
            canobj.data[3] = int(self.e_Data3_CAN1.get(), 16)
            canobj.data[4] = int(self.e_Data4_CAN1.get(), 16)
            canobj.data[5] = int(self.e_Data5_CAN1.get(), 16)
            canobj.data[6] = int(self.e_Data6_CAN1.get(), 16)
            canobj.data[7] = int(self.e_Data7_CAN1.get(), 16)

            # if canobj.ID == 0x5d3:
                # dev_logger.debug(f'0x5d3 send msg.......{hex(canobj.data[4]).zfill(4)}')
            # 两个Flag默认为零
            canobj.RemoteFlag = self.rtr_CAN1.get()
            canobj.ExternFlag = self.ext_CAN1.get()
            for i in range(3):
                self.ecan.Tramsmit(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1, canobj)

    def sendcan2(self):
        self.musbcanopen
        if self.musbcanopen == False:
            prod_logger.warning("Error, 请先打开设备")
            tkinter.messagebox.showinfo("ERROR", "请先打开设备")
        else:
            canobj = CAN_OBJ()
            canobj.ID = int(self.e_ID_CAN2.get(), 16)
            # canobj.DataLen = int(self.ct_Length_CAN2["value"])
            canobj.DataLen = int(self.ct_Length_CAN2_data.get())

            canobj.data[0] = int(self.e_Data0_CAN2.get(), 16)
            canobj.data[1] = int(self.e_Data1_CAN2.get(), 16)
            canobj.data[2] = int(self.e_Data2_CAN2.get(), 16)
            canobj.data[3] = int(self.e_Data3_CAN2.get(), 16)
            canobj.data[4] = int(self.e_Data4_CAN2.get(), 16)
            canobj.data[5] = int(self.e_Data5_CAN2.get(), 16)
            canobj.data[6] = int(self.e_Data6_CAN2.get(), 16)
            canobj.data[7] = int(self.e_Data7_CAN2.get(), 16)
            canobj.RemoteFlag = self.rtr_CAN2.get()
            canobj.ExternFlag = self.ext_CAN2.get()
            self.ecan.Tramsmit(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel2, canobj)

    def sendcan1_manual(
        self, ID=0x5D3, data_string=None, DataLen=8, RemoteFlag=0, ExternFlag=0
    ):
        if data_string is None:
            data = ["0", "0", "0", "0", "0", "0", "0", "0"]
        else:
            data = []
            for i in range(8):
                data.append(data_string[i * 2 : i * 2 + 2])
        
        if self.musbcanopen == False:
                # 非离线模式才走下面判断
            prod_logger.warning("Error, 请先打开设备")

            if not self.sendcan1_manual_offline:
                # tkinter.messagebox.showinfo("ERROR", "请先打开设备")
                result = tkinter.messagebox.askyesno('离线确认','是否采用设备离线模式运行？\n离线模式一旦选择后不可再退回!!')
                if result:
                    self.sendcan1_manual_offline = True
            return "error"

        else:
            canobj = CAN_OBJ()
            canobj.ID = int(ID)
            canobj.DataLen = int(DataLen)
            canobj.data[0] = int(data[0], 16)
            canobj.data[1] = int(data[1], 16)
            canobj.data[2] = int(data[2], 16)
            canobj.data[3] = int(data[3], 16)
            canobj.data[4] = int(data[4], 16)
            canobj.data[5] = int(data[5], 16)
            canobj.data[6] = int(data[6], 16)
            canobj.data[7] = int(data[7], 16)
            canobj.RemoteFlag = RemoteFlag
            canobj.ExternFlag = ExternFlag
            # FIXME： res 是实际发送成功的报文数量
            res = self.ecan.Tramsmit(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1, canobj)
            # dev_logger.debug(f'{type(res)}res:{res}')
            # [x]: res 大概多久会返回？？ 没返回的时候是不是堵塞程序？   已验证可行
            # [x]: 此处能否直接连续发3次，还是必须判断上一次返回成功，再发下一次? 已验证可行
            res += self.ecan.Tramsmit(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1, canobj)
            res += self.ecan.Tramsmit(UsbcanConstant.USBCAN2, UsbcanConstant.DevIndex, UsbcanConstant.Channel1, canobj)


            return res


class GcanViewTemplate(GcanViewBase):
    # 测试模块页面的初始化
    # 设定位置ID清单
    MODELIDLIST = []
    MAX_ROW = 8
    MAX_COLUMN = 8
    for i in range(MAX_ROW):
        for j in range(MAX_COLUMN):
            position = f"{i}{j}"
            MODELIDLIST.append(position)

    testmodelDict = {}
    # FunctestUnit 
    testfuncDict:dict[str,FunctestUnit]= {}
    for i, j in MODELIDLIST:
        # TESTMODELLIST[f"{i}{j}"] = f"模块-R{i}C{j}"
        testmodelDict[f"{i}{j}"] = TestDataStucture(PosId=f"{i}{j}")
        testfuncDict[f"{i}{j}"] = (
            lambda i, j: lambda: tkinter.messagebox.showinfo("提示", f"R{i}C{j}未定义!")
        )(i, j)

    TESTCONFIGDATALIST = []
    TESTCONFIGDATADICT = {}
    def setModelNameAndFunction(self):
        with open("config/unittestConfig.json", "r", encoding="utf-8") as fp:
            self.configJson = json.loads(fp.read())

        dev_logger.debug(self.configJson)
        for item in self.configJson.get("data"):
            # 每个模块的测试配置数据
            cur = TestDataStucture(**item)
            self.TESTCONFIGDATALIST.append(cur)
            self.TESTCONFIGDATADICT[cur.PosId] = cur

        for model in self.TESTCONFIGDATALIST:
            self.testmodelDict[model.PosId] = model

        # self.TESTFUNCLIST["00"] = self.testR0C0
        # self.TESTFUNCLIST["01"] = self.testR0C1

        # 先生产统一的函数,存在列表中,然后有个别特殊情况的,可以再后面更新函数
        self.generateTestfuncList()
        # 个别特殊需求的测试需求可以单独在这里定义覆盖掉之前的统一定义
        self.testfuncListupdate()

    def testfuncListupdate(self):
        """
        针对特殊需求的测试模块单独设定测试功能
        """
        self.testmodelDict["76"] = TestDataStucture(
            PosId="76",
            MsgId=0x280,
            name="发出发动机信号",
            testDatas=[
                UnitTestDataStructure("发动机信号", "0000000000000000", "0000000000000000")
            ],
        )

        self.testfuncDict["76"] = self.testR7C6

        self.testmodelDict["77"] = TestDataStucture(
            PosId="77",
            MsgId=0x570,
            name="整车上电信号",
            testDatas=[
                UnitTestDataStructure("整车上电", "0000000000000000", "0000000000000000")
            ],
        )

        self.testfuncDict["77"] = self.testR7C7

        self.testfuncDict["10"] = self.testR1C0
        self.testfuncDict["11"] = self.testR1C1

        self.testfuncDict["12"] = self.testR1C2
        self.testfuncDict["13"] = self.testR1C3

        pass

    def createUnitTestFramewrapper(self, posid):
        def inner():
            PosId = posid
            name = self.testmodelDict[posid].name
            MsgId = self.testmodelDict[posid].MsgId
            testDatas = self.testmodelDict[posid].testDatas
            rcevId = self.testmodelDict[posid].rcevId
            curToplevel = FunctestUnit(
                self,rcevId=rcevId, PosId=PosId, name=name, MsgId=MsgId, testDatas=testDatas
            )
            
            # 设置窗口置顶，确保设置的窗口在主窗口上面
            curToplevel.attributes('-topmost', True)
            return curToplevel

        return inner

    def generateTestfuncList(self):

        for posid in self.testmodelDict:
            self.testfuncDict[posid] = self.createUnitTestFramewrapper(posid)

    def testR7C6(self):
        modelR7C6 = FunctestR7C6(self, PosId="76", name="发动机信号模拟")
        modelR7C6.withdraw()
        modelR7C6.sendDataBtnDict[0].invoke()

    def testR7C7(self):
        modelR7C7 = FunctestR7C7(self, PosId="77", name="整车上电信号模拟")
        modelR7C7.withdraw()
        modelR7C7.sendDataBtnDict[0].invoke()
    
    def testR1C0(self):
        def inner():
            posid = '10'
            PosId = posid
            name = self.testmodelDict[posid].name
            MsgId = self.testmodelDict[posid].MsgId
            testDatas = self.testmodelDict[posid].testDatas
            rcevId = self.testmodelDict[posid].rcevId
            curToplevel =  FunctestUnitR1C0_R1C1(
                self,rcevId=rcevId, PosId=PosId, name=name, MsgId=MsgId, testDatas=testDatas
            )
            
            # 设置窗口置顶，确保设置的窗口在主窗口上面
            curToplevel.attributes('-topmost', True)
            return curToplevel
        return inner()

    def testR1C1(self):
        def inner():
            posid = '11'
            PosId = posid
            name = self.testmodelDict[posid].name
            MsgId = self.testmodelDict[posid].MsgId
            testDatas = self.testmodelDict[posid].testDatas
            rcevId = self.testmodelDict[posid].rcevId
            curToplevel =  FunctestUnitR1C0_R1C1(
                self,rcevId=rcevId, PosId=PosId, name=name, MsgId=MsgId, testDatas=testDatas
            )
            
            # 设置窗口置顶，确保设置的窗口在主窗口上面
            curToplevel.attributes('-topmost', True)
            return curToplevel
        return inner()

    def testR1C2(self):
        def inner():
            posid = '12'
            PosId = posid
            name = self.testmodelDict[posid].name
            MsgId = self.testmodelDict[posid].MsgId
            testDatas = self.testmodelDict[posid].testDatas
            rcevId = self.testmodelDict[posid].rcevId
            curToplevel =  FunctestUnitR1C2_R1C3(
                self,rcevId=rcevId, PosId=PosId, name=name, MsgId=MsgId, testDatas=testDatas
            )
            
            # 设置窗口置顶，确保设置的窗口在主窗口上面
            curToplevel.attributes('-topmost', True)
            return curToplevel

        # 借用createUnitTestFramewrapper，所以需要再inner后面加上括弧
        # self.testfuncDict[posid] = self.createUnitTestFramewrapper(posid)
        return inner()

    def testR1C3(self):
        def inner():
            posid = '13'
            PosId = posid
            name = self.testmodelDict[posid].name
            MsgId = self.testmodelDict[posid].MsgId
            testDatas = self.testmodelDict[posid].testDatas
            rcevId = self.testmodelDict[posid].rcevId
            curToplevel =  FunctestUnitR1C2_R1C3(
                self,rcevId=rcevId, PosId=PosId, name=name, MsgId=MsgId, testDatas=testDatas
            )
            
            # 设置窗口置顶，确保设置的窗口在主窗口上面
            curToplevel.attributes('-topmost', True)
            return curToplevel

        # 借用createUnitTestFramewrapper，所以需要再inner后面加上括弧
        # self.testfuncDict[posid] = self.createUnitTestFramewrapper(posid)
        return inner()
